package com.zhanglijie.improve.aqs.mix.readwrite;

import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

import com.zhanglijie.improve.aqs.exclusive.MyAbstractQueuedSynchronizer;

import sun.misc.Unsafe;
import sun.security.provider.SHA;

/**
 * @author zhanglijie
 * @version 1.0
 * @since 1.1.0 2022/10/26 0026 18:25
 */
public class MyReentrantLockReadWrite implements ReadWriteLock {
    private static MyReentrantLockReadWrite.ReadLock readLock;
    private static MyReentrantLockReadWrite.WriteLock writeLock;
    private MySync sync;
    public MyReentrantLockReadWrite(){
        this(false);
    }
    public MyReentrantLockReadWrite(boolean isFair){
        sync = isFair? new MyFairSync():new MyNoFairSync();
        readLock = new ReadLock(this);
        writeLock = new WriteLock(this);
    }



    static class MyFairSync extends MySync{
        @Override
        public boolean writeShouldBlock(){
            return hasQueuePred();
        }
        @Override
        public boolean readShouldBlock(){
            return hasQueuePred();
        }
    }

    /**
     * 因为读线程可以不互斥 而写线程获得锁的唯一条件就是没有任何线程包括写|读线程占有锁。因此导致写线程获得锁的几率很低
     * 所以在非公平情况下 为了让写线程获得锁的几率高点 所以写优先策略,读线程不能盲目一上来就抢（判断条件就是如果下个要唤醒的是写 让写优先）
     */
    static class MyNoFairSync extends MySync{
        @Override
        public boolean writeShouldBlock(){
            return false;//非公平的一定要让写线程多一些强到锁的机会  写优先 // writers can always barge
        }
        @Override
        public boolean readShouldBlock(){
            return apparentlyFirstQueuedIsExclusive();
        }


    }

    static abstract class MySync extends MyAbstractQueuedSynchronizer{
        private static int SHARED_SHIFT = 16;//高16位是读  低16位是写
        private static int SHARED_UNIT = (1<<SHARED_SHIFT);//每次增加的读状态
        private static int  MAX_COUNT= (1<<SHARED_SHIFT)-1;//读状态或者写状态的最大数量65535  0000 0000 0000 0000 | 1111 1111 1111 1111
        private static int EXECLUSIVE_MASK = (1<<SHARED_SHIFT)-1;// 0000 0000 0000 0000 | 1111 1111 1111 1111
        abstract boolean writeShouldBlock();
        abstract boolean readShouldBlock();
        //第一个获取读锁的线程
        private volatile Thread firstReader = null;
        //第一个获取读锁线程的重入次数
        private volatile int firstReaderHolderCount;

        /**
         * 存放每个线程的重入次数和tid
         */
        static class HoldCounter{
            private int count=0;
            private long tid = getThreadId(Thread.currentThread());
        }


        private HoldCounter cachedHoldCounter;
        static class ThreadLocalHoldCount extends ThreadLocal<HoldCounter>{
            @Override
            public HoldCounter initialValue(){
                return new HoldCounter();
            }
        }
        private ThreadLocalHoldCount readHolds;
        static int sharedCount(int state){
            return state>>>SHARED_SHIFT;
        }


        @Override
        protected boolean isHeldExclusively() {
            return getOwnerThread() == Thread.currentThread();
        }
        static int exclusiveCount(int state){
            return state & EXECLUSIVE_MASK;
        }
        //这里需要实现独占锁和共享锁的四个方法
        MySync(){
            readHolds = new ThreadLocalHoldCount();
            setState(getState());
        }



        /**
         * 读锁获取锁（第一次尝试 可以写有先 后面尝试readShouldBlock看看是否重入 重入可以不管写优先）
         * @param arg
         * @return
         */
        @Override
        protected int tryAcquireShared(int arg){
            Thread currentThread = Thread.currentThread();
            int state = getState();
            //有写锁 但是不是本线程（不是锁降级情况）--不能获取成功
            if(exclusiveCount(state) != 0 && currentThread != getOwnerThread()){
                return -1;
            }
            //获取当前读锁
            int readCount = sharedCount(state);
            //cas+1操作 cas成功的只有一个线程进入  成功获得锁后需要重入逻辑处理  所以里面全是重入业务处理
            if(!readShouldBlock() && readCount<MAX_COUNT && compareAndSetState(state,state+SHARED_UNIT)){
                //难点：读锁的重入性
                //第一个获取读锁的线程 区别对待（每一轮第一次获取读锁的记录）
                if(readCount == 0){
                    firstReader = currentThread;
                    firstReaderHolderCount = 1;

                }else if(firstReader == currentThread){//和第一次获取读锁线程是相同线程 说明第二次是重入
                    //重入
                    firstReaderHolderCount++;
                }else{//其余情况表示：当前线程不是第一个获取读锁的线程
                    //这边会区别第二个和之后的
                    //cachedHoldCounter最近一次获取读锁的线程id和计数
                    HoldCounter rh = cachedHoldCounter;
                    //如果rh==null 表示当前线程是第二个线程
                    //rh.tid != getThreadId(currentThread) 表示第3个及其之后的线程
                    if(rh == null || rh.tid != getThreadId(currentThread)){
                        cachedHoldCounter = rh = readHolds.get();
                    }
                    //第二轮 第二个线程和第一轮最后一个获得锁的读锁线程相同才会走这个else if 因为第一轮已经释放了所有读锁 就直接用上轮的 下面rh.count++就行了
                    else if(rh.count == 0){
                        readHolds.set(rh);
                    }
                    rh.count++;
                }
                return 1;
            }
            //失败的线程会走到这里，再次进行尝试
            return fullyTryAcquireShared(currentThread);


        }

        /**
         * 完全尝试获取锁
         * @param currentThread
         * @return
         */
        private int fullyTryAcquireShared(Thread currentThread){
            HoldCounter rh = null;
            //利用自旋不断尝试获取读锁
            for(;;){
                int state = getState();
                //再次判断写锁是否被持有,只有锁降级可以拿
                if(exclusiveCount(state) != 0 && getOwnerThread() != currentThread){
                   return -1;
                }
                else if(readShouldBlock()){//读锁如果队列有等待了 也要做重入处理
                    //return -1;
                    //这里不应该直接返回-1 而是应该继续判断重入的情况（这个如果命中 一般是第一次尝试时候有写锁优先情况）
                    if(firstReader == currentThread){
                        //这里只是判断是重入的情况，表示确实是重入 此时直接放行不处理 会到下面cas++操作
                    }
                    //不是第一个获取读锁的线程
                    else{
                        if(rh == null){
                            rh = cachedHoldCounter;
                            if(rh == null || rh.tid != getThreadId(currentThread)){
                                //这里获得当前线程重入次数
                                rh = readHolds.get();
                                //这里成立表示当前线程没有拿到过读锁
                                if(rh.count == 0){//这个下面第二次会走到下面的if(rh.cout)处理
                                    /**
                                     * 维持一个规则：（抢到有值才有意义否则一律再次remove())
                                     * 如果没有持有读锁 那么肯定没有threadlocal中的holdCounter对象，这个一旦new出来
                                     * 会出现多个线程都去new一个 会出现版本不统一的脏数据 而上面第一次尝试直接拿出来++是因为cas成功了
                                     * 而我这个是for(;;)自旋抢
                                     */
                                    readHolds.remove();
                                }
                            }
                        }
                        //走到这里表示当前线程 并且这个rh还是上面if赋值的 && cout==0表示第一次来拿锁此时不是重入 只能乖乖去排队 返回-1
                        if(rh.count == 0){
                            return -1;
                        }

                    }
                }
                /**
                 * 下面才是合法的重入
                 */
                if(sharedCount(state) == MAX_COUNT){
                    throw new Error("读锁超出范围");
                }
                if(compareAndSetState(state,state+SHARED_UNIT)){
                    //这里又要处理重入的善后工作
                    if(sharedCount(state) == 0){
                        firstReader = currentThread;
                        firstReaderHolderCount = 1;
                    }
                    else if(firstReader == currentThread){
                        firstReaderHolderCount++;
                    }
                    else {
                        if(rh == null ){
                            rh = cachedHoldCounter;
                        }
                        if(rh == null || rh.tid != getThreadId(currentThread)){
                            rh = readHolds.get();
                        }
                        else if(rh.count == 0){
                            readHolds.set(rh);
                        }
                        rh.count++;
                        cachedHoldCounter = rh;
                    }
                    return 1;
                }

            }
        }

        /**
         * 读锁释放锁
         *  释放没有那么多相获取锁那样条件才能获得
         * @param arg
         * @return
         */
        @Override
        protected boolean tryReleaseShared(int arg){
            Thread currentThread = Thread.currentThread();
            if(firstReader == currentThread){
                //非重入
                if(firstReaderHolderCount == 1){
                    firstReader = null;
                }
                //有重入
                else{
                    firstReaderHolderCount--;
                }
            }
            else{
                HoldCounter rh = cachedHoldCounter;
                if(rh == null || rh.tid != getThreadId(currentThread)){
                    rh = readHolds.get();//执行完这个rh就是当前线程了
                }
                //走到这里就表示当前线程了,获取当前线程的重入次数
                int count = rh.count;
                if(count <= 1){
                    readHolds.remove();
                    if(count <= 0){
                        throw new Error("没有获得锁的线程不能释放");
                    }
                }
                --rh.count;
            }
            //走到这里表示重入的操作已经完毕了，但是state总数还需要处理
            for(;;){
                int state = getState();
                int i = state-SHARED_UNIT;
                if(compareAndSetState(state,i)){
                    //读锁和写锁都是放干净了下面return ==true
                    return i==0;//为什么一定读写都干净了 因为这个一旦return true意味着要去阻塞队列唤醒节点了
                }
            }
        }

        /**
         * 写锁获取锁
         * @param arg
         * @return
         */
        @Override
        public boolean tryAcquire(int arg){
            Thread currentThread = Thread.currentThread();
            int state = getState();
            int w = exclusiveCount(state);
            //总数不等于0 说明有锁不知道是读锁还是写锁
            if(state != 0){//这个分支能跳过第一个判断的只有当前线程 也就是只有一个线程成功 所以下面setState()是安全的因为只有一个线程能通过
                //w等于0表示读锁不为0，说明有读线程拿着读锁执行读操作还没有释放，所以写线程不能获取写锁
                //假设w!=0 说明有写线程拿着写锁但是这个写线程不是当前线程这时候不是重入，所以当前线程不能获取写锁
                if(w == 0 || currentThread != getOwnerThread()){
                    return false;
                }
                //原有写锁数量+要加入的数量超出最大线程阈值
                if((w+exclusiveCount(arg)) > MAX_COUNT){
                    throw new Error("超出最大线程数量限制");
                }
                //重置读写锁总数
                setState(state+arg);
                return true;
            }
            //走到这里表示state==0 此时既没有读锁也没有写锁 可以cas竞争
            if(writeShouldBlock() || !compareAndSetState(state,state+arg)){
                return false;
            }
            //走到这里表示cas成功或者队列没人排队
            setOwnerThread(currentThread);
            return true;

        }

        /**
         * 写锁释放锁 - 这个方法只有一个线程能够执行
         * @param arg
         * @return
         */
        @Override
        public boolean tryRelease(int arg){
            //判断当前持有锁的线程是否是当前线程 如果不是 那么没有权限释放锁
           if(!isHeldExclusively()){
               throw new IllegalMonitorStateException();
           }
           //计算新的state总数
            int newState = getState()- arg;
           boolean free = exclusiveCount(newState) == 0;
           if(free){
               //如果写锁数量==0 就表示当前完全释放锁
               setOwnerThread(null);
           }
           setState(newState);
           return free;
        }
    }
     class ReadLock implements Lock{
        public ReadLock(MyReentrantLockReadWrite lock){
            sync = lock.sync;
        }

        @Override
        public void lock() {
            sync.acquireShared(1);
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {

        }

        @Override
        public boolean tryLock() {
            return false;
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            return false;
        }

        @Override
        public void unlock() {
            sync.releaseShared(1);
        }

        @Override
        public Condition newCondition() {
            return null;
        }
    }
    class WriteLock implements Lock{
        public WriteLock(MyReentrantLockReadWrite lock){
            sync = lock.sync;
        }
        @Override
        public void lock() {
            sync.acquire(1);
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {

        }

        @Override
        public boolean tryLock() {
            return false;
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            return false;
        }

        @Override
        public void unlock() {
            sync.release(1);
        }

        @Override
        public Condition newCondition() {
            return null;
        }
    }
    @Override
    public Lock readLock() {
        return readLock;
    }

    @Override
    public Lock writeLock() {
        return writeLock;
    }

    /**
     * 从内存中获得线程的tid --要是thread 的tid被volatile修饰就不用这样子拿 直接get即可
     *    private long tid;
     * @param thread
     * @return
     */
    static long getThreadId(Thread thread){
       return UNSAFE.getLongVolatile(thread, TID_OFFSET);
    }

    private static Unsafe UNSAFE = getUnsafe();
    private static long TID_OFFSET ;
    static{
        try {
            TID_OFFSET = UNSAFE.objectFieldOffset(Thread.class.getDeclaredField("tid"));
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    private static Unsafe getUnsafe(){

        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            Unsafe obj = (Unsafe) theUnsafe.get(null);//静态变量给个null 实例变量给当前实例对象
            return obj ;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}
